<template>
  <div>
    <el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭推拽">
    </el-switch>
    <el-button v-if="draggable" @click="batchSave">批量保存</el-button>
    <el-button type="danger" @click="batchDelete">删除</el-button>
    <!-- 这里的代码是我们直接从官网复制的树状结构代码 -->
    <el-tree :data="menus" :draggable="draggable" :allow-drop="allowDrop" :props="defaultProps" show-checkbox :expand-on-click-node="false" node-key="catId"
      :default-expanded-keys="expandedKey" @node-drop="handleDrop" ref="menuTree">
      <!-- 
        上面的
        :expand-on-click-node="false"的意思是当我们点击文字的时候不会自动展开/折叠，如果不设这个属性，我们的菜单就会点击append和remove按钮的时候展开和折叠 
        show-checkbox:表示我们的节点是否可以被选择
        node-key:每个树节点用来作为唯一标识的属性，整棵树应该是唯一的
        :default-expanded-keys:用来指定默认展开的菜单，是指绑定的是下面的data中的变量，里面设置的是在我们node-key中绑定的唯一标识,值为一个数组
      -->
      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <!-- 设置我们的节点小于或等于二级的时候才会显示Append按钮,也就是说我们的三级菜单是与允许显示我们的append按钮的 -->
          <el-button v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)">
            Append
          </el-button>
          <el-button v-if="node.childNodes.length == 0" type="text" size="mini" @click="() => edit(data)">
            Edit
          </el-button>
          <!-- 设置我们的子节点中的长度为0/没有子节点的时候才会显示它的删除按钮 -->
          <el-button v-if="node.childNodes.length == 0" type="text" size="mini" @click="() => remove(node, data)">
            Delete
          </el-button>
        </span>
      </span>
    </el-tree>

    <!-- 以下是我们的新增和修改的模态窗 -->
    <el-dialog :title="title" :visible.sync="dialogVisible" :close-on-click-modal="false">
      <el-form :model="category">
        <el-form-item label="菜单名称" :label-width="'10%'">
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData">确 定</el-button>
      </span>
    </el-dialog>
</div>
</template>

<script>
export default {
  data() {
    return {
      // 用来存放我们当前节点的父id
      pCid:[],
      // 用来存放我们拖拽之后须要向服务器发送请求进行修改的列表
      updateNodes: [],
      // 用来记录当前节点的总层数(我们拖动的时候的拖动节点的总层数)
      maxLevel:0,
      // 用来定义我们是否开启拖拽功能，如果一直开启着这个功能可能会导致我们的误拖
      draggable:false,
      // 用来定义我们的模态框上的标题的(显示这个是一个什么窗口)
      title: "",
      // 用来设置我们的模态框现在是属于什么功能的，add则为以添加功能进行显示，如果是edit则表示以修改功能进行显示
      dialogType: "",
      // 用来记录我们修改和新增时候的数据的
      category: {
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0
      },
      // 用来标识我们的模态框是否显示的
      dialogVisible: false,
      menus: [],
      // 设置需要默认展开的分类
      expandedKey: [],
      defaultProps: {
        // 这里是用来指定在我们后台给出的数据中哪个属性中是二级/下一级菜单(data[0].children)
        children: 'children',
        // 用来记录我们的数据中哪个键中的值需要显示出来(data[0].name)
        label: 'name'
      }
    }
  },
  methods: {
    getMenus() {
      this.$http({
        url: this.$http.adornUrl('/product/category/list/tree'),
        method: 'get'
        // 在这里使用{data}可以直接取出我们返回的对象中的键为data的值并把这个值传入到我们的入参data中
      }).then(({ data }) => {
        // console.log("成功获取到菜单数据",data.data);
        // 把我们的对象赋值给我们的data对象，使得我们的树状结构数据进行更新
        this.menus = data.data;
      })
    },
    /**
     * 用来批量删除的按钮
     */
    batchDelete(){
      // 用来存放我们所有需要删除的id的
      let catIds=[];
      let checkedNodes=this.$refs.menuTree.getCheckedNodes();
      console.log(checkedNodes);
      for (let i = 0; i < checkedNodes.length; i++) {
        catIds.push(checkedNodes[i].catId);
      }
      this.$confirm(`是否删除【${catIds}】菜单`, '提示000', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.$http({
            url:this.$http.adornUrl("/product/category/delete"),
            method:"post",
            data:this.$http.adornData(catIds,false)
          }).then(({data})=>{
            this.$message({
              type: 'success',
              message: '菜单批量删除成功!'
            });
            this.getMenus();
          })
        }).catch(() => {
          this.$message({
            type: 'error',
            message: '已取消删除'
          });          
        });
    },
    /**
     * 这个方法是用来一键保存我们的所有拖拽代码的方法
     */
    batchSave(){
      this.$http({
        url:this.$http.adornUrl("/product/category/update/sort"),
        method:"post",
        data:this.$http.adornData(this.updateNodes,false)
      }).then(({data})=>{
        this.$message({
          message:"菜单顺序等修改成功",
          type:"success"
        });
        // 刷新出新的菜单
        this.getMenus();
        // 设置需要默认展开的菜单
        this.expandedKey=this.pCid;
        this.updateNodes = [];
        this.maxLevel = 0;
      })
    },
    /**
     * 这个方法是用来在每次拖拽的时候判断是否能放进去的方法
     * @param {我们现在正在拖动的节点} raggingNode 
     * @param {目标节点} dropNode 
     * @param {用来表示拖拽到了哪里(prev目标节点前|inner插入至目标节点|next目标节点后)} type 
     */
    allowDrop(draggingNode, dropNode, type){
      // console.log('进行了拖拽，分别得到三个值：');
      // console.log('第一个值：',raggingNode);
      // console.log('第二个值：',dropNode);
      // console.log('第三个值：',type);
      
      // 1、被拖动的当前节点以及所在的父节点总数不能大于3
      //    1.1、获取被拖动的节点的总层数
      this.countNodeLevel(draggingNode);
      //    1.2、获取我们正在拖动的几点的层数
      // console.log(this.maxLevel,draggingNode.level);
      let deep=this.maxLevel-draggingNode.level+1;
      this.maxLevel=0;
      // 2、根据不同情况来做不同的操作
      //    2.1、如果我们拖动的位置是inner，则表示我们移动后的最大深度不能超过我们的父子的深度之和
      // console.log(type,":",deep,type=="inner"?dropNode.level:dropNode.parent.level)
      if(type=="inner"){
        return deep+dropNode.level<=3;
      }else{
        //  2.2、如果我们拖动的位置不是inner，则表示我们移动之后的最大深度是我们现在的菜单的层级加上我们的父菜单的层级
        return deep+dropNode.parent.level<=3;
      }
    },
    /**
     * 当我们拖拽完成时候触发的方法
     * @param {被拖拽节点对应的Node} draggingNode 
     * @param {结束拖拽时最后进入的节点} dropNode 
     * @param {被拖拽节点的放置位置,（before、after、inner）} dropType 
     * @param {event} ev 
     */
    handleDrop(draggingNode,dropNode,dropType,ev){
      // 创建一个pCid用来暂时存放我们的父id
      let pCid=0;
      // 定义一个变量用来存放我们移动节点之后的所有兄弟节点，来方便我们进行修改
      let siblings=null;
      // 1、获取当前节点最新的父节点id
      // console.log("拖拽完成：",draggingNode,dropNode,dropType,ev);
      // 根据我们拖拽节点放入的位置不同我们需要用不同的方式来找我们的兄弟节点
      if(dropType=="before"||dropType=="after"){
        // 1.1、如果是拖入的我们的节点前面后后面获取我们的兄弟节点作为我们的父节点id
        pCid=dropNode.parent.data.catId;
        // 获取我们的所有子节点
        siblings=dropNode.parent.childNodes;
      }else{
        pCid=dropNode.data.catId;
        siblings=dropNode.childNodes;
      }
      this.pCid.push(pCid);
      // 2、对当前拖拽节点的最新顺序放入我们的准备好的修改列表中
      for(let i=0;i<siblings.length;i++){
        //  2.1、把我们拖拽节点的所有兄弟节点进行遍历，修改所有兄弟节点的排列顺序
        if(siblings[i].data.catId==draggingNode.data.catId){
          // 获取我们当前节点的层次(我们先把旧的层次拿出来)
          let catLevel = draggingNode.level;
          // 如果新旧层次相同则无需改变，如果不同则需要修改所有的子层层级
          if(siblings[i].level!=catLevel){
            // 当前层次发生了变化
            catLevel=siblings[i].level;
            // 修改他子节点的层次
            this.updateChildNodeLevel(siblings[i]);
          }
        //  2.2、如果我们的当前遍历到的节点是我们的正在拖动的节点的话，我们需要给它设置新的父id
          this.updateNodes.push({catId:siblings[i].data.catId,sort:i,parentCid:pCid,catLevel:catLevel});
        }else{
          this.updateNodes.push({catId:siblings[i].data.catId,sort:i});
        }
      }
      

      // 3、给我们拖拽的所有节点修改节点的最新层次
    },
    updateChildNodeLevel(node){
      // 如果子菜单中有内容，则进行遍历
      if(node.childNodes!=null&&node.childNodes.length!=0){
        for(let i=0;i<node.childNodes.length;i++){
          // 获取我们数据库中的原始数据
          let cNode=node.childNodes[i].data;
          // 把我们的修改后测层级放入我们的需要修改的列表中
          this.updateNodes.push({catId:cNode.catId,catLevel:node.childNodes[i].level});
          // 递归查看，修改所有的子层级
          this.updateChildNodeLevel(node.childNodes[i]);
        }
      }
    },
    countNodeLevel(node){
      // 如果有子节点则继续计算字节点，如果没有子节点，则直接使用现在的层数
      if(node.childNodes!=null&&node.childNodes.length!=0){
        for(let i=0;i<node.childNodes.length;i++){
          // 如果我们的等级超过我们的最深长度则重新刷新我们的最深长度
          if(node.childNodes[i].level>this.maxLevel){
            this.maxLevel=node.childNodes[i].level;
          }
          this.countNodeLevel(node.childNodes[i]);
        }
      }else if(node.level>this.maxLevel){
        this.maxLevel=node.level;
      }
    },
    append(data) {
      console.log('append', data);
      // 设置我们的模态框功能
      this.dialogType = "add";
      // 设置模态框的标题
      this.title = "添加分类";
      // 设置我们的模态框进行显示
      this.dialogVisible = true;
      // 初始化我们的表单数据

      // 父id
      this.category.parentCid = data.catId;
      // 新增数据等级，是现在点击的菜单的儿子，所以需要我们现在的菜单+1
      // 这里的乘1是用来把我们的字符串转换为数字的
      this.category.catLevel = data.catLevel * 1 + 1;
      // 设置我们的数据的id,我们的数据id需要后台进行生成
      this.category.catId = null;
      // 定义我们的菜单名称为空
      this.category.name = "";
      // 定义我们的图标为空
      this.category.icon = "";
      // 设置我们的计量单位(我暂时也不知道这个字段是什么东西)
      this.category.productUnit = "";
      // 设置我们的菜单的排序优先级
      this.category.sort = 0;
      // 设置我们的菜单是否显示[1显示,0不显示](刚刚创建怎么能够逻辑删除？)
      this.category.showStatus = 1
    },
    edit(data) {
      console.log("需要修改的数据", data);
      this.dialogType = "edit";
      this.title = "修改分类";
      // 设置我们的模态框开始显示出来
      this.dialogVisible = true;
      // 发送请求获取当前节点最新的数据
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get"
      }).then(({ data }) => {
        // console.log("进行查询返回的最新数据：",data);
        this.category.name = data.category.name;
        this.category.catId = data.category.catId;
        this.category.icon = data.category.icon;
        this.category.productUnit = data.category.productUnit;
        this.category.parentCid = data.category.parentCid;
      });
    },
    submitData() {
      if (this.dialogType == "add") {
        this.addCategory();
      }
      if (this.dialogType == "edit") {
        this.editCategory();
      }
    },
    addCategory() {
      console.log("提交我们的分类数据", this.category);
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false)
      }).then(({ data }) => {
        this.$message({
          message: "菜单保存成功",
          type: "success"
        });
        // 关闭对话框
        this.dialogVisible = false;
        // 刷新出新的菜单
        this.getMenus();
        // 设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid];
      });
    },
    editCategory() {
      let { catId, name, icon, productUnit } = this.category;
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        data: this.$http.adornData({ catId, name, icon, productUnit }, false)
      }).then(({ data }) => {
        this.$message({
          message: "菜单修改成功",
          type: "success"
        });
        // 关闭对话框
        this.dialogVisible = false;
        // 刷新出新的菜单
        this.getMenus();
        // 设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid];
      });
    },
    remove(node, data) {
      // console.log('remove',node,data);
      this.$confirm(`是否删除【${data.name}】菜单？`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        let ids = [data.catId];
        this.$http({
          url: this.$http.adornUrl('/product/category/delete'),
          method: 'post',
          data: this.$http.adornData(ids, false)
        }).then((data) => {
          // 把我们的对象赋值给我们的data对象，使得我们的树状结构数据进行更新
          this.$message({
            type: 'success',
            message: '菜单删除成功!'
          });
          // 进行刷新我们的菜单
          this.getMenus();
          // 设置需要默认展开的菜单
          this.expandedKey = [node.parent.data.catId];
          // console.log(this.expandedKey);
        })
      }).catch(() => {
        this.$message({
          type: 'error',
          message: '已取消删除'
        });
      });

    },
  },
  created() {
    this.getMenus();
  }
}
</script>

<style></style>